/*
* QNX License:
* Copyright 2010, QNX Software Systems. All Rights Reserved.
*
* You must obtain a written license from and pay applicable license fees to QNX
* Software Systems before you may reproduce, modify or distribute this software,
* or any work that includes all or part of this software.   Free development
* licenses are available for evaluation and non-commercial purposes.  For more
* information visit http://licensing.qnx.com or email licensing@qnx.com.
*
* This file may contain contributions from others.  Please review this entire
* file for other proprietary rights or license notices, as well as the QNX
* Development Suite License Guide at http://licensing.qnx.com/license-guide/
* for other information.
*/
package views 
{
	import caurina.transitions.Tweener;
	
	import events.NavControllerEvent;
	
	import flash.display.DisplayObject;
	import flash.display.MovieClip;
	import flash.display.Sprite;
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;
	import flash.utils.getDefinitionByName;
	
	
	/**
	 * Dispatched when the view has started to change.
	 * @eventType qnx.ui.events.NavControllerEvent
	 */
	
	[Event(name = "navControllerEvent_view_changing", type = "events.NavControllerEvent")]
	
	/**
	 * Dispatched when the view change operation has completed.
	 * @eventType qnx.ui.events.NavControllerEvent
	 */
	
	[Event(name = "navControllerEvent_view_changed", type = "events.NavControllerEvent")]
	
	/**
	 * @private
	 * The NavController class handles switching and transitions between views in the HMI. 
	 * 
	 * @see qnx.views.UIViewStates
	 * @see qnx.views.UIView
	 */ 
	public class NavController extends EventDispatcher {
		
		
		private static const POSITION_LEFT:String ="positionLeft";
		private static const POSITION_RIGHT:String = "positionRight";
		
		private var __dataProvider			:	Array;
		private var __selectedIndex			: 	int;
		private var __container				:	Sprite;
		private var __viewField				:	String ="view";
		private var __viewInstanceField		:	String = "viewInstance";
		
		private var __isAnimating			:	Boolean = false;
		
		/**
		 * @private
		 * The current view. Null by default.
		 * 
		 */
		
		protected var currentView			: 	IView = null;
		
		/**
		 * @private
		 * The next view. Null by default.
		 */
		protected var nextView				:	IView = null;
		/** @private **/
		protected var __nextView			:	Object;
		
		/**
		 * Creates a NavController instance given a target.
		 * 
		 * @param	target The target for which to create the NavController.
		 */
		
		
		public function NavController(target : IEventDispatcher = null) {
			super(target);
		}
		/*
		* DataProvider - takes an array 
		* TODO Move to the DataProvider class
		*/
		public function set dataProvider(theArray:Array):void{
			__dataProvider = theArray;
		}
		
		/**
		 * Gets or sets the <code>DataProvider</code> for the view.
		 */
		
		public function get dataProvider():Array{
			return(__dataProvider);
		}
		
		/**
		 * Gets or sets the field <code>String</code>.
		 *
		 * <p>
		 * The String is inspected to create or access views. 
		 * </p>
		 * @param theString The <code>String</code> identifying the view. 
		 * @returns void
		 */
		public function set viewField(theString:String):void{
			__viewField = theString;
		}
		/**
		 * Gets or sets the field <code>String</code>.
		 *
		 * <p>
		 * The <code>String</code> is inspected to create or access views. 
		 * </p>
		 * @returns String 
		 */
		public function get viewField():String{
			return(__viewField);
		}
		
		public function set viewInstanceField(theString:String):void{
			__viewInstanceField = theString;
		}
		
		/**
		 * Gets or sets a <code>String</code> representing the instance of the view.
		 */
		public function get viewInstanceField():String{
			return(__viewInstanceField);
		}
		
		
		/**
		 * Container sprite used to contain the views 
		 */
		public function set container(theContainer:Sprite):void{
			__container = theContainer;
		}
		
		/**
		 * Gets or sets the container sprite used to contain the views. 
		 */
		public function get container():Sprite{
			return(__container);
		}
		
		// Helps to retain which item is selected for easier lookups
		public function set selectedIndex(ind:int):void{
			__selectedIndex = ind;
		}
		
		/**
		 * Gets or sets the selected index. 
		 */
		
		public function get selectedIndex():int{
			return(__selectedIndex);
		}
		//-------------------------------------
		
		/**
		 * Changes the view. 
		 * <p>
		 * This function can be called externally.
		 * </p>
		 * @params view A <code>String</code> or class reference representing a view object. 
		 */
		public function changeSection(view:Object):void{
			//trace("NavController.changeSection(view:Object)");
			// this holds no logic. just tells the item to switch the views
			switchView(view);
		}
		
		
		/** @private **/
		protected function switchView(view:Object):void{
			//trace("NavController.switchView(view:Object) > view:"+view);
			if(view!=null){
				var index:int = getViewIndex(view);
				if(!currentView)
				{
					//trace("NavController.switchView(view:Object) > No Current View. First time being set");
					currentView = addView( view );
					currentView.state = UIViewStates.ANIMATION_IN_START;
					currentView.state = UIViewStates.ANIMATION_IN_COMPLETE;
					selectedIndex = index;
				}else
				{
					if(index!=selectedIndex)
					{
						__nextView = view;
						
						//trace("NavController.switchView(view:Object) > Current View found. Set the next view");
						// check for tweening here.
						if(nextView){return;}
						changeView();
						__nextView = null;
					}else
					{
						throw(new Error("Error switching views > view to change to is same as current view"));
					}
				}
			}
		}
		
		/** @private **/
		protected function changeView():void
		{
			var index:int = getViewIndex(__nextView);
			nextView = addView(__nextView);
			determineViewDirection(selectedIndex,index);
			selectedIndex = index;
		}
		
		/** @private **/
		protected function addView(view:Object):IView{
			//trace("NavController.addView(view:Object)");
			var newView:IView = createView(view) as IView;
			//TODO : SM > Change movieclip to Sprite for lowest common 
			// Add child at 0 means we don't redraw the entire display list
			MovieClip(container).addChildAt(DisplayObject(newView), 0);
			return(newView);
		}
		
		/** @private **/
		protected function pushView(view:Object):void{
			//trace("NavController.pushView(view:Object)");
			positionAndAnimateView(view,POSITION_RIGHT);
		}
		
		/** @private **/
		protected function popView(view:Object):void{
			//trace("NavController.popView(view:Object)");
			positionAndAnimateView(view,POSITION_LEFT);
		}
		
		/** @private **/
		protected function positionView(view:Object,thePosition:String=null):void{
			//trace("NavController.positionView(view:Object,thePosition:String) > view:"+view+", thePosition :"+thePosition);
			// Default x an y to the container x and y for the first run
			var x:Number = container.x;
			var y:Number = container.y;
			var theView:DisplayObject = view as DisplayObject;
			switch(thePosition){
				case POSITION_LEFT:
					x = 0 - container.stage.stageWidth;
					y = container.y;
					break;
				case POSITION_RIGHT:
					x = container.stage.stageWidth;
					y = container.y;
					break;	
			}
			
			theView.x = x;
			//			theView.y = y;
		}
		
		/** @private **/
		/* EVENTS ------------------------------- */
		protected function willChange():void{
			//trace("NavController.willChange()");
			dispatch(NavControllerEvent.VIEW_CHANGING);
		}
		
		/** @private **/
		protected function didChange():void{
			//trace("NavController.didChange()");
			dispatch(NavControllerEvent.VIEW_CHANGED);
		}
		/* END OF EVENTS-------------------------*/
		/** @private **/
		private function positionAndAnimateView(theView:Object,thePosition:String):void{
			
			//trace("NavController.positionAndAnimateView(theView:Object,thePosition:String");
			willChange();
			positionView(theView,thePosition);
			animateViews();
		}
		
		/** @private **/
		protected function animateViews():void
		{
			
			//trace("NavController.animateViews()");
			
			var newView:IView = nextView;
			var oldView:IView = currentView;
			
			oldView.state = UIViewStates.ANIMATION_OUT_START;
			newView.state = UIViewStates.ANIMATION_IN_START;
			
			//Determine the xpos for the old view to go to
			var oldXpos:Number = (DisplayObject(newView).x > 0 ) ? (-1*container.stage.stageWidth) : container.stage.stageWidth;
			// TODO - Refactor - this shouldn't be needed 
			
			
			Tweener.addTween(oldView,{x:oldXpos,onComplete:onAnimationComplete,time:.5, transition:"easeinoutquad"});
			Tweener.addTween(newView,{x:0,time:.5, transition:"easeinoutquad"});
		}
		
		/** @private **/
		protected function onAnimationComplete():void{
			//trace("NavController.onAnimationComplete()");
			var oldView:IView = currentView;
			oldView.state = UIViewStates.ANIMATION_OUT_COMPLETE;
			removeItem(oldView);
			onViewChanged();
		}
		/** @private **/
		// SM Not sure why i wrapped this, but felt like i should, in case other things need to be done
		protected function onViewChanged():void{
			__isAnimating = false;
			currentView = nextView;
			currentView.state = UIViewStates.ANIMATION_IN_COMPLETE;
			
			nextView = null;
			didChange();
			if(__nextView!=null)
			{
				switchView(__nextView);
			}
		}
		/** @private **/
		/* Remove for listeners etc if necessary*/
		protected function removeItem(item:IView):void{
			// TODO : refactor for sprite or something.
			//trace("NavController.removeItem(item)");
			
			MovieClip(container).removeChild(DisplayObject(item));
		}
		
		/** @private **/
		private function determineViewDirection(oldIndex:int,newIndex:int):void{
			//trace("NavController.determineViewDirection(oldIndex,newIndex)");
			if(oldIndex < newIndex){
				pushView(nextView);
			}else
			{	
				popView(nextView);
			}
		}
		/** @private **/
		// TODO : Stacey > must be re-visited
		private function getViewIndex(theView:Object):Number
		{
			//trace("NavController.getViewIndex(theView:Object)");
			var len:int = __dataProvider.length;
			for(var i:int =0;i<len;i++){
				var vo:Object = __dataProvider[i] as Object;//TODO Change this typing
				// TODO Not sure if this is what julian had in mind
				var view:Object = vo[__viewField] as Object;
				if(view == theView){
					return(i);
				}
			}
			return(-1);
		}
		
		
		/* Currently Not used
		private function getViewExists(view:Object):Boolean{
		var doesExist:Boolean = false;
		var ind:int = this.getViewIndex(view);
		var vo:Object = this.getItemAt(ind);
		
		if(vo[__viewInstanceField]){
		doesExist = true;
		}
		else{
		doesExist = false;
		}
		
		return(doesExist);
		
		}
		* 
		* 
		private function getViewAtIndex(theIndex:int):DisplayObject
		{
		var vo:Object = getItemAt(theIndex);
		var returnView:DisplayObject = vo[__viewInstanceField] as DisplayObject;
		return(returnView);
		}
		private function getItemAt(theIndex:int):Object{
		if(theIndex > __dataProvider.length-1 || theIndex < 0)
		{
		throw(new Error("Range Error: index must be within supplied bounds"));
		return;	
		}
		return(__dataProvider[theIndex]);	
		}
		* */
		/** @private **/
		// Not sure how to use this quite yet... 
		private function createView(theView:Object):DisplayObject{
			//trace("NavController.createView(theView:Object)");
			var returnView:Object;
			// If the view is a string
			if(theView is String){		
				var objClass:Class = Class(getDefinitionByName(theView as String));
				returnView = new objClass();
			}
			else if(theView is Class){
				returnView = new theView();
			}
			else if(theView is DisplayObject){
				returnView = theView;
			}
			else{
				throw(new Error("Creating a view , must be an instance, Class name String or Class"));
			}
			
			return(returnView as DisplayObject);
		}
		/** @private **/
		private function dispatch(eventType:String):void
		{
			var event:NavControllerEvent = new NavControllerEvent(eventType);
			dispatchEvent(event);
		}	
		
		
	}
}